package com.mic.yzzn.plc.core.control.handler;

import com.mic.yzzn.plc.common.control.datastructure.PLCData;
import com.mic.yzzn.plc.common.control.datastructure.PLCMission;
import com.mic.yzzn.plc.common.control.datastructure.PLCMissionResult;
import com.mic.yzzn.plc.common.ins.datastructure.PLCInstructionTuple;
import com.mic.yzzn.plc.common.instructions.basic.PLCRequestInstruction;
import com.mic.yzzn.plc.core.control.datastructure.COMStationConfig;
import com.mic.yzzn.plc.core.control.datastructure.PLCStationConfig;
import com.mic.yzzn.plc.core.ins.toolUtils.ExceptionToString;
import com.mic.yzzn.plc.core.internet.COMInstructionDispatcher;
import com.mic.yzzn.plc.core.plcevents.PLCFeedbackEvent;
import com.mic.yzzn.plc.core.plcevents.listener.PLCFeedbackEventListener;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import purejavacomm.NoSuchPortException;
import purejavacomm.PortInUseException;
import purejavacomm.UnsupportedCommOperationException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TooManyListenersException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Created by ASH on 16/7/18.
 *
 * 用于处理 COM 口采集的任务的 handler
 */
public class COMMissionHandler implements PLCMissionHandler, PLCFeedbackEventListener {

    private COMInstructionDispatcher instructionDispatcher;
    private COMStationConfig stationConfig;

    private HashMap<PLCRequestInstruction, UUID> cycleInstuctions = new HashMap<>();
    private HashMap<PLCRequestInstruction, UUID> readInstructions = new HashMap<>();
    private HashMap<PLCRequestInstruction, UUID> writeInstructions = new HashMap<>();

    private HashMap<UUID, PLCMissionResult> readInsResults = new HashMap<>();
    private HashMap<UUID, PLCMissionResult> writeInsResults = new HashMap<>();

    private ConcurrentHashMap<String, PLCData> cycleResultMap;
    private ConcurrentHashMap<UUID, PLCMissionResult> readResultMap;
    private ConcurrentHashMap<UUID, PLCMissionResult> writeResultMap;

    private int maxComTimeoutTimes = 3;
    private int readTimeOut = 5;

    private CountDownLatch latch;

    private static final Logger logger = LogManager.getLogger(COMMissionHandler.class);

    @Override
    public void setStationConfig(PLCStationConfig stationConfig) throws Exception {
        this.stationConfig = (COMStationConfig) stationConfig;
        instructionDispatcher = new COMInstructionDispatcher(
                this.stationConfig.getBaudRate(),
                this.stationConfig.getDataBits(),
                this.stationConfig.getStopBit(),
                this.stationConfig.getParity(),
                this.stationConfig.getName()
        );
        instructionDispatcher.addListener(this);
    }

    @Override
    public long handleCycleMission(PLCMission mission, ConcurrentHashMap<String, PLCData> resultMap, int waitTime) {
        if (cycleResultMap == null) {
            cycleResultMap = resultMap;
        }

        logger.debug("Cycle: " + mission.getMissionID());
        PLCRequestInstruction ins = mission.getInstructions().get(0);
        cycleInstuctions.put(ins, mission.getMissionID());

        long begin = System.currentTimeMillis();
        send_block(ins);

        return System.currentTimeMillis() - begin;
    }

    @Override
    public void handleReadMission(PLCMission mission, ConcurrentHashMap<UUID, PLCMissionResult> resultMap, int waitTime) {
        if (readResultMap == null) {
            readResultMap = resultMap;
        }

        logger.debug("Read: " + mission.getMissionID());
        handleRealtimeMission(mission, true);
    }

    @Override
    public void handleWriteMission(PLCMission mission, ConcurrentHashMap<UUID, PLCMissionResult> resultMap, int waitTime) {
        if (writeResultMap == null) {
            writeResultMap = resultMap;
        }

        logger.debug("Write: " + mission.getMissionID());
        handleRealtimeMission(mission, false);
    }

    private void handleRealtimeMission(PLCMission mission, boolean isRead) {

        HashMap<PLCRequestInstruction, UUID> instructions = null;
        HashMap<UUID, PLCMissionResult> insResults = null;
        if (isRead) {
            instructions = readInstructions;
            insResults = readInsResults;
        } else {
            instructions = writeInstructions;
            insResults = writeInsResults;
        }

        UUID missionID = mission.getMissionID();
        PLCMissionResult result = new PLCMissionResult(missionID);
        result.expectInsSize = mission.getInstructions().size();
        insResults.put(missionID, result);
        for (PLCRequestInstruction ins : mission.getInstructions()) {
            instructions.put(ins, missionID);
            send_block(ins);
        }
    }

    private void send_block(PLCRequestInstruction ins) {
        int timeOutCount = 0;
        boolean latchOpen = false;
        do {
            instructionDispatcher.sendInstruction(ins);
            try {
                latch = new CountDownLatch(1);
                logger.debug("[wating for latch open]");
                latchOpen = latch.await(readTimeOut, TimeUnit.SECONDS); // 若当前指令发送后接收数据超时,则重新发送
                logger.debug("[latch open with state " + latchOpen + "]");
                if (!latchOpen) {
                    ++timeOutCount;
                    logger.error("[resend " + timeOutCount + " time]");
                    if (timeOutCount > maxComTimeoutTimes) {
                        logger.error("[reconnect]");
                        instructionDispatcher.disconnect();
                        instructionDispatcher.connect();
                        timeOutCount = 0;
                    }
                }
            } catch (Exception e) {
                logger.error(e, e);
            }
        } while (!latchOpen);
    }

    @Override
    public void notifyFeedbackEvent(PLCFeedbackEvent event) {
        logger.debug("[receive feedback " + event.getResult().responseString + " ]");
        PLCRequestInstruction rins = event.getResult().requestInstruction;
        ArrayList<PLCInstructionTuple> response = new ArrayList<>();
        response.add(event.getResult());

        HashMap<PLCRequestInstruction, UUID> instructions = null;
        HashMap<UUID, PLCMissionResult> insResults = null;
        ConcurrentHashMap<UUID, PLCMissionResult> resultMap = null;

        logger.debug("[find resultMap]");
        boolean find = false;
        boolean isRead = false;
        if (readInstructions.get(rins) != null) {
            instructions = readInstructions;
            insResults = readInsResults;
            resultMap = readResultMap;
            find = true;
            isRead = true;
        } else if (writeInstructions.get(rins) != null) {
            instructions = writeInstructions;
            insResults = writeInsResults;
            resultMap = writeResultMap;
            find = true;
        }

        if (find) {

            // 找出对应的result
            PLCMissionResult result = insResults.get(instructions.get(rins));
            result.initWithResponseInstruction(response);
            ++result.currentInsSize;
            // 移除已经返回的标记指令
            instructions.remove(rins);

            if (result.currentInsSize == result.expectInsSize) {
                resultMap.put(result.getResultID(), result);
                insResults.remove(result.getResultID());
            }
        } else {
            PLCMissionResult result = new PLCMissionResult(UUID.randomUUID());
            result.initWithResponseInstruction(response);
            // 将结果中的数据取出放入Map中
            for (PLCData data : result.getResultData())
            {
                cycleResultMap.put(data.getDataNumber(), data);
            }
        }

        latch.countDown();
        return;
    }
}
